Apollo Server & Error Handling
TL;DR
ApolloServer で Resolver 内から throw される例外は、最終的に ApolloError にラップされる
ApolloError にはスタックトレースの他にエラーコードも含まれ、クライアントはこれを参照してエラーの分類を知ることができる
組み込みのエラーには AuthenticationError, ForbiddenError, UserInputError がある
エラーレスポンスオブジェクトには、任意のフィールドを追加することもできる
カスタムエラーを投げたい場合は、ApolloError を継承したエラーを新たに定義するか、ApolloError 自体を throw するのが良い
Predefined errors
AuthenticationError
ForbiddenError
UserInputError
以下は、Apollo Server が独自に吐くエラーのように見える。
SyntaxError GraphQL のパースに失敗
ValidationError GraphQL の検証に失敗
PersistedQueryNotFoundError PersistedQuery が見つからない
PersistedQueryNotSupportedError PersistedQuery をサポートしていない
ApolloError
code:typescript
export class ApolloError extends Error implements GraphQLError {
public extensions: Record<string, any>;
readonly name;
readonly locations;
readonly path;
readonly source;
readonly positions;
readonly nodes;
public originalError;
constructor(
message: string,
code?: string,
properties?: Record<string, any>,
) {
super(message);
if (properties) {
Object.keys(properties).forEach(key => {
});
}
// if no name provided, use the default. defineProperty ensures that it stays non-enumerable
if (!this.name) {
Object.defineProperty(this, 'name', { value: 'ApolloError' });
}
// extensions are flattened to be included in the root of GraphQLError's, so
// don't add properties to extensions
this.extensions = { code };
}
}
例えば、UserInputError は以下のように定義される。
code:typescript
export class UserInputError extends ApolloError {
constructor(message: string, properties?: Record<string, any>) {
super(message, 'BAD_USER_INPUT', properties);
Object.defineProperty(this, 'name', { value: 'UserInputError' });
}
}
通常の Node.js の例外は、toApolloError で ApolloError に変換される。
code:typescript
export function toApolloError(
error: Error & { extensions?: Record<string, any> },
code: string = 'INTERNAL_SERVER_ERROR',
): Error & { extensions: Record<string, any> } {
let err = error;
if (err.extensions) {
err.extensions.code = code;
} else {
err.extensions = { code };
}
return err as Error & { extensions: Record<string, any> };
したがって、Apollo Server は、Resolver 内で例外が発行された際に、最終的にそれを ApolloError として処理する。そして、ApolloError にはエラーコードが含まれており、クライアントはこれを参照してエラーの分類を確認できる。toApolloError で変換されたエラーは、INTERNAL_SERVER_ERROR というエラーコードになる。 https://www.apollographql.com/docs/apollo-server/images/features/error-code.png
ApolloError に情報を追加する
任意のフィールドをエラーオブジェクトに追加できる。
code:typescript
const {
ApolloServer,
UserInputError,
gql,
} = require('apollo-server');
const typeDefs = gql`
type Mutation {
userInputError(input: String): String
}
`;
const resolvers = {
Mutation: {
userInputError: (parent, args, context, info) => {
if (args.input !== 'expected') {
throw new UserInputError('Form Arguments invalid', {
invalidArgs: Object.keys(args),
});
}
},
},
};
https://www.apollographql.com/docs/apollo-server/images/features/error-user-input.png